/*
 * Package il.ac.biu.cs.grossmm.test.cppToFooAdapter
 * File PresencePublicationAdapter.java
 * 
 * This is a part of presence service framework API. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.tests.fooAdapters;

import static il.ac.biu.cs.grossmm.api.data.SetOf.*;
import static il.ac.biu.cs.grossmm.api.keys.KeyFactory.*;
import static il.ac.biu.cs.grossmm.api.keys.PatternFactory.*;
import static il.ac.biu.cs.grossmm.api.presence.BaseVocabulary.*;
import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.Node;
import il.ac.biu.cs.grossmm.api.data.Nodeset;
import il.ac.biu.cs.grossmm.api.data.Root;
import il.ac.biu.cs.grossmm.api.data.SetOf;
import il.ac.biu.cs.grossmm.api.flow.DataProcessorBase;
import il.ac.biu.cs.grossmm.api.flow.NodeManipulator;
import il.ac.biu.cs.grossmm.api.flow.PublicationPoint;
import il.ac.biu.cs.grossmm.api.keys.Key;
import il.ac.biu.cs.grossmm.api.keys.KeyPattern;
import il.ac.biu.cs.grossmm.api.presence.Entity;
import il.ac.biu.cs.grossmm.api.presence.EntityType;
import il.ac.biu.cs.grossmm.api.presence.Registrar;
import il.ac.biu.cs.grossmm.api.presence.ResourceType;
import il.ac.biu.cs.grossmm.api.server.Component;
import il.ac.biu.cs.grossmm.foo.format.Presence;
import il.ac.biu.cs.grossmm.foo.format.PresenceDoc;
import il.ac.biu.cs.grossmm.foo.format.PresenceTuple;
import il.ac.biu.cs.grossmm.foo.format.PresenceDoc.Tuple;
import il.ac.biu.cs.grossmm.foo.protocol.PublishRequest;
import il.ac.biu.cs.grossmm.foo.protocol.Request;
import il.ac.biu.cs.grossmm.foo.protocol.Response;
import il.ac.biu.cs.grossmm.foo.protocol.ResponseCodes;
import il.ac.biu.cs.grossmm.foo.protocol.Session;
import il.ac.biu.cs.grossmm.foo.protocol.UserSession;
import il.ac.biu.cs.grossmm.api.server.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

public class PresencePublicationAdapter extends DataProcessorBase 
implements Service, Component, RequestHandler 
{
	static Logger logger = Logger.getLogger("PresencePublicationAdapter");
	
//	static {
//		logger.setLevel(Level.FINE);
//	}
	
	Registrar registrar;
	FooProvider provider;
	
	final static Nodeset<SetOf<Presence>, Presence> ns = setOf(Presence.class).NODESET;
	
	Lock pubLock = new ReentrantLock();
	
	PublicationPoint<SetOf<Presence>> pp;
	
	Map<String, Publications> allPubs
		= new HashMap<String, Publications>();
	
	public PresencePublicationAdapter()
	{
		super("Presence Publication Adapter");
	}
	
	static class Publication {
		public Publication(Node<Presence> root)
		{
			this.node = root;
		}

		Node<Presence> node;
	}
	
	static class Publications extends HashMap<String, Publication> {
		/**
		 * 
		 */
		private static final long serialVersionUID = -2052782085590787427L;
		Root<SetOf<Presence>> root;
		public Publications(Root<SetOf<Presence>> root)
		{
			this.root = root;
		}
	}
		

	@Override
	public void run()
	{
		KeyPattern pattern =
			pattern(Entity.class, 
				mandatory(RESOURCE_TYPE, ResourceType.PRESENCE)
			);
		
		try {
			registrar = componentManager.getByClass(Registrar.class);
			provider = componentManager.getByClass(FooProvider.class);
			
			pp= adm.createPublicationPoint(
				pattern, 
				setOf(Presence.class), 
				false, 
				getClass().getName());
			
		} catch (Exception e) {
			e.printStackTrace();
			componentManager.abort(e);
			stopped();
			return;
		}
		
		provider.registerRequestHandler(PublishRequest.class, this);
		
		started();
	}

	public void attached(UserSession session)
	{
		
	}

	public void dettached(UserSession session)
	{
		// TODO
	}

	public synchronized boolean requestReceived(Session session, Request request) throws OperationFailedException
	{
		assert request instanceof PublishRequest;
		
		PublishRequest pub = (PublishRequest) request;
		
//		session.send(new Response(request, ResponseCodes.OK));
//		if( 1==1 )
//			return true;
		
		if( logger.isLoggable(Level.FINE))
			logger.fine(request.toString());
		
		if(! pub.getPayloadType().equals("foo.PresenceDoc") )
			return false;
		
		
		
		String user = pub.getPresentity();
		UserSession u = session.getUserSession(user);
		
		
		
		if( u == null ) {
			session.send(new Response(request, ResponseCodes.UNAUTHORIZED));
			return true;
		}
		
		Entity presentity = u.getEntity();
		
		if(! presentity.is(EntityType.PRESENTITY) ) {
			session.send(new Response(request, ResponseCodes.FORBIDDEN));
			return true;
		}
		
		String pid = pub.getPid();
		
		
		
		pubLock.lock();
		
		Root<SetOf<Presence>> root;
		Node<Presence> foo;
		Node lockNode = null;
		NodeManipulator man = null ;
		
		try {
			Publications publications = allPubs.get(user);
						
			if( publications  == null ) {
				Key key = key(presentity, 
					entry(RESOURCE_TYPE, ResourceType.PRESENCE));
				root = pp.createNode(key, true);
				
				man = pp.writeLock(lockNode = root);
				foo = man.createSubnode(root, ns);
				
				Publication p = new Publication(foo);
				publications = new Publications(root);
				publications.put(pid, p);
				allPubs.put(user, publications);
			} else {
				Publication p = publications.get(pid);
				if( p == null ) {
					root = publications.root;
					man = pp.writeLock(lockNode = root);
					foo = man.createSubnode(root, ns);
					p = new Publication(foo);
					publications.put(pid, p);
				} else {
					foo = p.node;
					man = pp.writeLock(lockNode = foo);
				}
			}
			
			PresenceDoc doc = new PresenceDoc(pub.getPayload());
			
		
			updateNode(foo, man, doc);
			
			
		} finally {
			if( lockNode != null )
				pp.writeUnlock(lockNode);
			pubLock.unlock();
		}
		
		session.send(request.okResponse());
		return true;
	}

	/**
	 * @param foo
	 * @param man
	 * @param doc
	 * @throws OperationFailedException
	 */
	private void updateNode(Node<Presence> foo, NodeManipulator man, PresenceDoc doc) 
	throws OperationFailedException
	{
		man.setAndCheck(foo, Presence.ENTITY, doc.getEntity());
		man.setAndCheck(foo, Presence.ACTIVITIES, doc.getActivities());
		man.setAndCheck(foo, Presence.LOCATION, doc.getLocation());
		man.setAndCheck(foo, Presence.MOOD, doc.getMood());
		man.setAndCheck(foo, Presence.SPHERE, doc.getSphere());
		
		Tuple[] tuples = doc.getTuples();
		if( tuples != null )
			for( Tuple t : tuples) 
				updateTuple(foo, man, t);
		
		// remove all tuples with no corresponding tuples in the published doc
		removeUnpublished(foo, man, doc);
	}

	/**
	 * @param foo
	 * @param man
	 * @param doc
	 * @throws OperationFailedException
	 */
	private void removeUnpublished(Node<Presence> foo, NodeManipulator man, PresenceDoc doc) throws OperationFailedException
	{
		Set<Node<PresenceTuple>> fooTuples = man.getSubnodes(foo, Presence.TUPLES);
		
		List<Node<PresenceTuple>> toRemove = null;
		
		// get all tuples with no corresponding tuples in the published 
		// document
		for( Node<PresenceTuple> fooTuple : fooTuples )  {
			String id = man.getValue(fooTuple, PresenceTuple.ID);
			if( doc.getTuple(id) == null ) {  
				if( toRemove == null ) 
					toRemove = new ArrayList<Node<PresenceTuple>>();
				toRemove.add(fooTuple);
			}
		}
		
		// remove if any
		if( toRemove != null )
			for( Node<PresenceTuple> fooTuple : toRemove )
				man.removeSubnode(fooTuple);
	}

	/**
	 * @param foo
	 * @param man
	 * @param t
	 * @throws OperationFailedException
	 */
	private void updateTuple(Node<Presence> foo, NodeManipulator man, Tuple t) 
	throws OperationFailedException
	{
		String id= t.getId();
		Node<PresenceTuple> fooTuple = 
			man.getSubnode(foo, Presence.TUPLES, PresenceTuple.ID, id);
		
		if( fooTuple == null ) {
			fooTuple = man.createSubnode(foo, Presence.TUPLES);
			man.setValue(fooTuple, PresenceTuple.ID, id);
		}
		
		man.setAndCheck(fooTuple, PresenceTuple.ADDRESS, t.getAddress());
		man.setAndCheck(fooTuple, PresenceTuple.PRIORITY, t.getPriority());
		man.setAndCheck(fooTuple, PresenceTuple.STATUS, t.getStatus());
		man.setAndCheck(fooTuple, PresenceTuple.CLASS, t.getTupleClass());
		man.setAndCheck(fooTuple, PresenceTuple.CAPABILITIES, t.getCapabilities());
	}

	public void terminating(Session session)
	{
		System.out.println("Session closed");
	}
}
